package com.benshou.bcss.common.j17common;

import lombok.NonNull;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;

/**
 * @author <a href="mailto:wh1zper@qq.com">wh1zper</a>
 */
public class LazyWrapper<T> {

    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    @NonNull
    private final Supplier<? extends T> valueSupplier;

    private volatile Object[] valueHolder;

    public LazyWrapper(@NonNull Supplier<? extends T> valueSupplier) {
        this.valueSupplier = valueSupplier;
    }

    public T getValue() {
        var readLock = this.lock.readLock();
        readLock.lock();
        try {
            if (this.valueHolder != null) {
                @SuppressWarnings("unchecked")
                T value = (T) this.valueHolder[0];
                return value;
            }
        } finally {
            readLock.unlock();
        }

        var writeLock = this.lock.writeLock();
        writeLock.lock();
        try {
            if (this.valueHolder != null) {
                @SuppressWarnings("unchecked")
                T value = (T) this.valueHolder[0];
                return value;
            }

            var value = this.valueSupplier.get();
            this.valueHolder = new Object[]{value};
            return value;
        } finally {
            writeLock.unlock();
        }
    }

    public void clear() {
        var readLock = this.lock.readLock();
        readLock.lock();
        try {
            if (this.valueHolder == null) {
                return;
            }
        } finally {
            readLock.unlock();
        }

        var writeLock = this.lock.writeLock();
        writeLock.lock();
        try {
            this.valueHolder = null;
        } finally {
            writeLock.unlock();
        }
    }

    public boolean hasValue() {
        var readLock = this.lock.readLock();
        readLock.lock();
        try {
            return this.valueHolder != null;
        } finally {
            readLock.unlock();
        }
    }
}
